//
//  TypesConversionBasicTests.swift
//  SWXMLHash
//
//  Copyright (c) 2016 David Mohundro
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//

import SWXMLHash
import Testing

// swiftlint:disable identifier_name
// swiftlint:disable file_length
// swiftlint:disable line_length
// swiftlint:disable type_body_length

struct SampleUserInfo {
    enum ApiVersion {
        case v1
        case v2
    }

    var apiVersion = ApiVersion.v2

    func suffix() -> String {
        if apiVersion == ApiVersion.v1 {
            return " (v1)"
        } else {
            return ""
        }
    }

    static let key = CodingUserInfoKey(rawValue: "test")!

    init(apiVersion: ApiVersion) {
        self.apiVersion = apiVersion
    }
}

struct TypeConversionBasicTypesTests {
    var parser: XMLIndexer?
    let xmlWithBasicTypes = """
        <root>
          <string>the string value</string>
          <int>100</int>
          <double>100.45</double>
          <float>44.12</float>
          <bool1>0</bool1>
          <bool2>true</bool2>
          <empty></empty>
          <basicItem id="1234">
            <name>the name of basic item</name>
            <price>99.14</price>
          </basicItem>
          <attr string=\"stringValue\" int=\"200\" double=\"200.15\" float=\"205.42\" bool1=\"0\" bool2=\"true\"/>
          <attributeItem name=\"the name of attribute item\" price=\"19.99\"/>
        </root>
    """

    init() {
        parser = XMLHash.parse(xmlWithBasicTypes)
    }

    @Test
    func shouldConvertValueToNonOptional() {
        do {
            let value: String = try parser!["root"]["string"].value()
            #expect(value == "the string value")
        } catch {
            Issue.record("\(error)")
        }
    }

    @Test
    func shouldConvertEmptyToNonOptional() {
        do {
            let value: String = try parser!["root"]["empty"].value()
            #expect(value.isEmpty)
        } catch {
            Issue.record("\(error)")
        }
    }

    @Test
    func shouldThrowWhenConvertingMissingToNonOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["missing"].value() as String)
        }
    }

    @Test
    func shouldConvertValueToOptional() {
        do {
            let value: String? = try parser!["root"]["string"].value()
            #expect(value == "the string value")
        } catch {
            Issue.record("\(error)")
        }
    }

    @Test
    func shouldConvertEmptyToOptional() {
        do {
            let value: String? = try parser!["root"]["empty"].value()
            #expect(value?.isEmpty == true)
        } catch {
            Issue.record("\(error)")
        }
    }

    @Test
    func shouldConvertMissingToOptional() {
        do {
            let value: String? = try parser!["root"]["missing"].value()
            #expect(value == nil)
        } catch {
            Issue.record("\(error)")
        }
    }

    @Test
    func shouldConvertAttributeToNonOptional() {
        do {
            let value: String = try parser!["root"]["attr"].value(ofAttribute: "string")
            #expect(value == "stringValue")
        } catch {
            Issue.record("\(error)")
        }
    }

    @Test
    func shouldConvertAttributeToOptional() {
        let value: String? = parser!["root"]["attr"].value(ofAttribute: "string")
        #expect(value == "stringValue")
    }

    @Test
    func shouldThrowWhenConvertingMissingAttributeToNonOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["attr"].value(ofAttribute: "missing") as String)
        }
    }

    @Test
    func shouldConvertMissingAttributeToOptional() {
        let value: String? = parser!["root"]["attr"].value(ofAttribute: "missing")
        #expect(value == nil)
    }

    @Test
    func shouldConvertAttributeToNonOptionalWithStringRawRepresentable() {
        enum Keys: String {
            case string
        }
        do {
            let value: String = try parser!["root"]["attr"].value(ofAttribute: Keys.string)
            #expect(value == "stringValue")
        } catch {
            Issue.record("\(error)")
        }
    }

    @Test
    func shouldConvertAttributeToOptionalWithStringRawRepresentable() {
        enum Keys: String {
            case string
        }
        let value: String? = parser!["root"]["attr"].value(ofAttribute: Keys.string)
        #expect(value == "stringValue")
    }

    @Test
    func shouldThrowWhenConvertingMissingAttributeToNonOptionalWithStringRawRepresentable() {
        enum Keys: String {
            case missing
        }
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["attr"].value(ofAttribute: Keys.missing) as String)
        }
    }

    @Test
    func shouldConvertMissingAttributeToOptionalWithStringRawRepresentable() {
        enum Keys: String {
            case missing
        }
        let value: String? = parser!["root"]["attr"].value(ofAttribute: Keys.missing)
        #expect(value == nil)
    }

    func testIntShouldConvertValueToNonOptional() {
        do {
            let value: Int = try parser!["root"]["int"].value()
            #expect(value == 100)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testIntShouldThrowWhenConvertingEmptyToNonOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["empty"].value() as Int)
        }
    }

    func testIntShouldThrowWhenConvertingMissingToNonOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["missing"].value() as Int)
        }
    }

    func testIntShouldConvertValueToOptional() {
        do {
            let value: Int? = try parser!["root"]["int"].value()
            #expect(value == 100)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testIntShouldConvertEmptyToOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["empty"].value() as Int?)
        }
    }

    func testIntShouldConvertMissingToOptional() {
        do {
            let value: Int? = try parser!["root"]["missing"].value()
            #expect(value == nil)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testIntShouldConvertAttributeToNonOptional() {
        do {
            let value: Int = try parser!["root"]["attr"].value(ofAttribute: "int")
            #expect(value == 200)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testIntShouldConvertAttributeToOptional() {
        let value: Int? = parser!["root"]["attr"].value(ofAttribute: "int")
        #expect(value == 200)
    }

    func testDoubleShouldConvertValueToNonOptional() {
        do {
            let value: Double = try parser!["root"]["double"].value()
            #expect(value == 100.45)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testDoubleShouldThrowWhenConvertingEmptyToNonOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["empty"].value() as Double)
        }
    }

    func testDoubleShouldThrowWhenConvertingMissingToNonOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["missing"].value() as Double)
        }
    }

    func testDoubleShouldConvertValueToOptional() {
        do {
            let value: Double? = try parser!["root"]["double"].value()
            #expect(value == 100.45)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testDoubleShouldConvertEmptyToOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["empty"].value() as Double?)
        }
    }

    func testDoubleShouldConvertMissingToOptional() {
        do {
            let value: Double? = try parser!["root"]["missing"].value()
            #expect(value == nil)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testDoubleShouldConvertAttributeToNonOptional() {
        do {
            let value: Double = try parser!["root"]["attr"].value(ofAttribute: "double")
            #expect(value == 200.15)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testDoubleShouldConvertAttributeToOptional() {
        let value: Double? = parser!["root"]["attr"].value(ofAttribute: "double")
        #expect(value == 200.15)
    }

    func testFloatShouldConvertValueToNonOptional() {
        do {
            let value: Float = try parser!["root"]["float"].value()
            #expect(value == 44.12)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testFloatShouldThrowWhenConvertingEmptyToNonOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["empty"].value() as Float)
        }
    }

    func testFloatShouldThrowWhenConvertingMissingToNonOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["missing"].value() as Float)
        }
    }

    func testFloatShouldConvertValueToOptional() {
        do {
            let value: Float? = try parser!["root"]["float"].value()
            #expect(value == 44.12)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testFloatShouldConvertEmptyToOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["empty"].value() as Float?)
        }
    }

    func testFloatShouldConvertMissingToOptional() {
        do {
            let value: Float? = try parser!["root"]["missing"].value()
            #expect(value == nil)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testFloatShouldConvertAttributeToNonOptional() {
        do {
            let value: Float = try parser!["root"]["attr"].value(ofAttribute: "float")
            #expect(value == 205.42)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testFloatShouldConvertAttributeToOptional() {
        let value: Float? = parser!["root"]["attr"].value(ofAttribute: "float")
        #expect(value == 205.42)
    }

    func testBoolShouldConvertValueToNonOptional() {
        do {
            let value1: Bool = try parser!["root"]["bool1"].value()
            let value2: Bool = try parser!["root"]["bool2"].value()
            #expect(!value1)
            #expect(value2)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testBoolShouldThrowWhenConvertingEmptyToNonOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["empty"].value() as Bool)
        }
    }

    func testBoolShouldThrowWhenConvertingMissingToNonOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["missing"].value() as Bool)
        }
    }

    func testBoolShouldConvertValueToOptional() {
        do {
            let value1: Bool? = try parser!["root"]["bool1"].value()
            #expect(value1 == false)
            let value2: Bool? = try parser!["root"]["bool2"].value()
            #expect(value2 == true)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testBoolShouldConvertEmptyToOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["empty"].value() as Bool?)
        }
    }

    func testBoolShouldConvertMissingToOptional() {
        do {
            let value: Bool? = try parser!["root"]["missing"].value()
            #expect(value == nil)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testBoolShouldConvertAttributeToNonOptional() {
        do {
            let value: Bool = try parser!["root"]["attr"].value(ofAttribute: "bool1")
            #expect(!value)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testBoolShouldConvertAttributeToOptional() {
        let value: Bool? = parser!["root"]["attr"].value(ofAttribute: "bool2")
        #expect(value == true)
    }

    let correctBasicItem = BasicItem(name: "the name of basic item", price: 99.14, id: "1234")

    func testBasicItemShouldConvertBasicItemToNonOptional() {
        do {
            let value: BasicItem = try parser!["root"]["basicItem"].value()
            #expect(value == correctBasicItem)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testBasicItemShouldThrowWhenConvertingEmptyToNonOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["empty"].value() as BasicItem)
        }
    }

    func testBasicItemShouldThrowWhenConvertingMissingToNonOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["missing"].value() as BasicItem)
        }
    }

    func testBasicItemShouldConvertBasicItemToOptional() {
        do {
            let value: BasicItem? = try parser!["root"]["basicItem"].value()
            #expect(value == correctBasicItem)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testBasicItemShouldConvertEmptyToOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["empty"].value() as BasicItem?)
        }
    }

    func testBasicItemShouldConvertMissingToOptional() {
        do {
            let value: BasicItem? = try parser!["root"]["missing"].value()
            #expect(value == nil)
        } catch {
            Issue.record("\(error)")
        }
    }

    let correctAttributeItem = AttributeItem(name: "the name of attribute item", price: 19.99)
    let correctAttributeItemStringRawRepresentable = AttributeItemStringRawRepresentable(name: "the name of attribute item", price: 19.99)

    func testAttributeItemShouldConvertAttributeItemToNonOptional() {
        do {
            let value: AttributeItem = try parser!["root"]["attributeItem"].value()
            #expect(value == correctAttributeItem)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testAttributeItemStringRawRepresentableShouldConvertAttributeItemToNonOptional() {
        do {
            let value: AttributeItemStringRawRepresentable = try parser!["root"]["attributeItem"].value()
            #expect(value == correctAttributeItemStringRawRepresentable)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testAttributeItemShouldThrowWhenConvertingEmptyToNonOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["empty"].value() as AttributeItem)
        }
    }

    func testAttributeItemShouldThrowWhenConvertingMissingToNonOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["missing"].value() as AttributeItem)
        }
    }

    func testAttributeItemShouldConvertAttributeItemToOptional() {
        do {
            let value: AttributeItem? = try parser!["root"]["attributeItem"].value()
            #expect(value == correctAttributeItem)
        } catch {
            Issue.record("\(error)")
        }
    }

    func testAttributeItemShouldConvertEmptyToOptional() {
        #expect(throws: XMLDeserializationError.self) {
            try (parser!["root"]["empty"].value() as AttributeItem?)
        }
    }

    func testAttributeItemShouldConvertMissingToOptional() {
        do {
            let value: AttributeItem? = try parser!["root"]["missing"].value()
            #expect(value == nil)
        } catch {
            Issue.record("\(error)")
        }
    }

    @Test
    func shouldBeAbleToGetUserInfoDuringDeserialization() {
        let parser = XMLHash.config { config in
            let options = SampleUserInfo(apiVersion: .v1)
            config.userInfo = [ SampleUserInfo.key: options ]
        }.parse(xmlWithBasicTypes)

        do {
            let value: BasicItem = try parser["root"]["basicItem"].value()
            #expect(value.name == "the name of basic item (v1)")
        } catch {
            Issue.record("\(error)")
        }
    }
}

enum BasicItemValidation: Error { case priceOutOfBounds(Double) }

struct BasicItem: XMLObjectDeserialization {
    let name: String
    let price: Double
    let id: String

    static func deserialize(_ node: XMLIndexer) throws -> BasicItem {
        var name: String = try node["name"].value()

        if let opts = node.userInfo[SampleUserInfo.key] as? SampleUserInfo {
            name += opts.suffix()
        }

        return try BasicItem(
            name: name,
            price: node["price"].value(),
            id: node.value(ofAttribute: "id")
        )
    }
}

extension BasicItem: Equatable {
    static func == (a: BasicItem, b: BasicItem) -> Bool {
        a.name == b.name && a.price == b.price
    }
}

struct AttributeItem: XMLElementDeserializable {
    let name: String
    let price: Double

    static func deserialize(_ element: SWXMLHash.XMLElement) throws -> AttributeItem {
        print("my deserialize")
        return try AttributeItem(
            name: element.value(ofAttribute: "name"),
            price: element.value(ofAttribute: "price")
        )
    }
}

extension AttributeItem: Equatable {
    static func == (a: AttributeItem, b: AttributeItem) -> Bool {
        a.name == b.name && a.price == b.price
    }
}

struct AttributeItemStringRawRepresentable: XMLElementDeserializable {
    private enum Keys: String {
        case name
        case price
    }

    let name: String
    let price: Double

    static func deserialize(_ element: SWXMLHash.XMLElement) throws -> AttributeItemStringRawRepresentable {
        print("my deserialize")
        return try AttributeItemStringRawRepresentable(
            name: element.value(ofAttribute: Keys.name),
            price: element.value(ofAttribute: Keys.price)
        )
    }
}

extension AttributeItemStringRawRepresentable: Equatable {
    static func == (a: AttributeItemStringRawRepresentable, b: AttributeItemStringRawRepresentable) -> Bool {
        a.name == b.name && a.price == b.price
    }
}

// swiftlint:enable identifier_name
// swiftlint:enable line_length
// swiftlint:enable type_body_length
